#define _CRT_SECURE_NO_DEPRECATE 1
#include "../contrib/utf8/ConvertUTF.h"
#include <iostream>
#include "rs_string.h"
#include "rs_log.h"
using namespace std;

void wstring_to_ascii (const std::wstring &s, std::string &t)
{
    const size_t max_str_len=10000000;
    size_t len=s.length();
    if (len+1>=max_str_len)
        throw "qq";
    static char tt[max_str_len];
    static wchar_t ss[max_str_len];
    wcsncpy (ss, s.c_str(), len);
    for (size_t i=0; i<len; i++)
        tt[i] = (char) ss[i];  // todo: check for characters over 8-bit range
    tt[len]='\0';
    t = tt;
}

void ascii_to_wstring (const std::string &s, std::wstring &t)
{
    const size_t max_str_len=10000000;
    size_t len=s.length();
    if (len+1>=max_str_len)
        throw "qq";
    static wchar_t tt[max_str_len];
    static char ss[max_str_len];
    strncpy (ss, s.c_str(), len);
    for (size_t i=0; i<len; i++)
        tt[i] = ss[i];
    tt[len]=L'\0';
    t = tt;
}


void utf8_to_wstring (const std::string &s, std::wstring &t)
{
    const int max_uni_buf=10000000;
    static wchar_t uni_buf[max_uni_buf+1];
    unsigned src_len = unsigned(s.length());
    if (src_len>=max_uni_buf) {
        cout << "can't convert utf8 string to utf16 string: too long, "
            << src_len;
        return;
    }
    if (s.length() == 0) {
        t=L"";
        return;
    }
    const UTF8* src_start=(UTF8*) (s.c_str());
    const UTF8* src_end=src_start + src_len;
    UTF16* tgt_start=(UTF16*) (uni_buf);
    UTF16* tgt_end=tgt_start+max_uni_buf;
    ConversionResult result;
    result = ConvertUTF8toUTF16 (&src_start, src_end, &tgt_start, tgt_end, lenientConversion);
    // todo: check result to be conversionOK
/*
    int char_count = 0;
    char_count = MultiByteToWideChar(CP_UTF8, 0, 
        s.c_str(), (int) s.length(), uni_buf, max_uni_buf);
    if (char_count == 0) {
        cout << "can't convert utf-8 to ucs-16";
        return;
    }
    uni_buf [char_count] = L'\0';
    */
    *tgt_start = L'\0';
    t = uni_buf;
}


void wstring_to_utf8 (const rs_string&s, string& t)
{
    const int max_utf8_buf=10000000;
    static char utf8_buf[max_utf8_buf+1];
    unsigned src_len = unsigned(s.length());
    if (src_len>=max_utf8_buf) {
        cout << "can't convert utf8 string to utf16 string: too long, "
            << src_len;
        return;
    }
    size_t char_count = 0;
    if (src_len == 0) {
        t="";
        return;
    }
    const UTF16* src_start=(UTF16*) (s.c_str());
    const UTF16* src_end=src_start + src_len;
    UTF8* tgt_start=(UTF8*) (utf8_buf);
    UTF8* tgt_end=tgt_start+max_utf8_buf;
    ConversionResult result;
    result = ConvertUTF16toUTF8 (&src_start, src_end, &tgt_start, tgt_end, lenientConversion);

    // todo: check result to be conversionOK
    /*
    char_count = WideCharToMultiByte(CP_UTF8, 0, s.c_str(), (int) s.length(), utf8_buf,
        max_utf8_buf, NULL, NULL);
    int result=0;
    if (char_count == 0) {
        result = GetLastError();
        cout << "can't convert wstring to utf-8 string";
        return;
    }
    utf8_buf [char_count] = L'\0';
    */
    *tgt_start = '\0';
    t = utf8_buf;
}


void to_lower (wchar_t &wc)
{
    if (wc>=L'A' && wc<=L'Z')
        wc += 32;
}

void to_lower (string &s)
{
    size_t len=s.length();
    for (size_t i=0; i<len; i++)
        to_lower (s[i]);
}


void to_lower (char &c)
{
    if (c>='A' && c<='Z')
        c += 32;
}

void to_lower (rs_string &s)
{
    size_t len=s.length();
    for (size_t i=0; i<len; i++)
        to_lower (s[i]);
}


void to_upper (rs_string &s)
{
    size_t len=s.length();
    for (size_t i=0; i<len; i++)
        to_upper (s[i]);
}


void to_upper (wchar_t &wc)
{
    if (wc>=L'a' && wc<=L'z')
        wc -= 32;
}

bool is_nonascii (const wchar_t wc)
{
    return wc>L'~'  ||  wc<L' ';
}

bool is_upper (const wchar_t wc)
{
    return wc>=L'A' && wc<=L'Z';
}

inline bool is_lower (const wchar_t wc)
{
    return wc>=L'a' && wc<=L'z';
}

bool is_lower (const rs_string& str)
{
    size_t str_len = str.length();
    size_t i;
    for (i=0; i< str_len; i++) {
        if (!is_lower(str[i]))
            return false;
    }
    return true;
}


bool is_alpha (const wchar_t wc/*, wd_mode wdm*/)
{
    if (wc>=L'a' && wc<=L'z')
        return true;
    if (wc>=L'A' && wc<=L'Z')
        return true;
    /*
    if (wdm == wdm_basic)
        return false;
    if (wdm == wdm_ident) {
        if (wc==L'_'  ||  wc>=L'0'  &&  wc<=L'9')
        return true;
    }
    if (wc==L'\'' || wc==L'-')
        return true;
    if (wdm == wdm_qd)
        return false;
    if (wc==L'\\' || wc==L'&')
        return true;
        */
    return false;
}


bool is_ident_char (const wchar_t wc)
{
    if (wc>=L'a' && wc<=L'z')
        return true;
    if (wc>=L'A' && wc<=L'Z')
        return true;
    if (wc==L'_'  ||  wc>=L'0'  &&  wc<=L'9')
        return true;
    return false;
}



void trim_all_non_ident_chars (rs_string &s)
{ // trims initial and final non-lowercase-letters
    while (s.length()>0) {
        if (is_ident_char(s[0]))
            break;
        else
            s = s.substr (1);
    }
    while (s.length()>0) {
        if (is_ident_char(s[s.length()-1]))
            break;
        else
            s = s.substr (0, s.length()-1);
    }
}


void trim_spaces (rs_string &s)
{ // trims initial and final spaces (' ') and tabulators ('\t')
    unsigned len = s.length();
    if (len==0)
        return;
    unsigned first = len; // first assumed non-space character position
    unsigned last = 0; // last assumed non-space character position
    unsigned i;
    for (i=0; i<len; i++) {
        if (s[i]!=L' ' && s[i]!=L'\t') {
            first = i;
            break;
        }
    }
    for (i=len; i>0; i--) {
        if (s[i-1]!=L' ' && s[i-1]!=L'\t') {
            last = i;
            break;
        }
    }
    s = s.substr (first, last - first);
}


bool is_alpha (const rs_string &wsrc)
{ // true if all characters are alphabetic
    size_t str_len = wsrc.length();
    size_t i;
    for (i=0; i<str_len; i++) {
        if (!is_alpha (wsrc[i]/*, wdm_basic*/))
            return false;
    }
    return true;
}


bool is_any_alpha (const rs_string &wsrc)
{ // true if any character is alphabetic
    size_t str_len = wsrc.length();
    size_t i;
    for (i=0; i<str_len; i++) {
        if (is_alpha (wsrc[i]/*, wdm_qd*/))
            return true;
    }
    return false;
}


void replace_all (string& str, char a_find, char a_replace)
{
    size_t str_len = str.length();
    char *cstr=(char*)str.c_str();
    for (size_t i=0; i<str_len; i++) {
        if (cstr[i]==a_find)
            cstr[i]=a_replace;
    }
}

void replace_all (rs_string& str, wchar_t a_find, wchar_t a_replace)
{
    size_t str_len = str.length();
    wchar_t *cstr=(wchar_t*)str.c_str();
    for (size_t i=0; i<str_len; i++) {
        if (cstr[i]==a_find)
            cstr[i]=a_replace;
    }
}


void replace_file_substring (rs_string& str, const wchar_t *a_find, const wchar_t *a_replace)
{ // replaces all occurences of a_find with a_replace (if found)
    // assumes that character '|' is not part of a_find and a_replace
    replace_all (str, a_find, L"|");
    replace_all (str, L"|", a_replace);
}

void replace_all (rs_string& str, const wchar_t *a_find, const wchar_t *a_replace)
{
    // todo: replace_all: check that new string doesn't include old string
    // todo: use rs_string for all arguments???
    size_t find_len = wcslen(a_find);
    while (1) {
        size_t pos = str.find (a_find);
        if (pos == string::npos)
            return;
        str.replace (pos, find_len, a_replace);
    }
}

void replace_all (string& str, const char *a_find, const char *a_replace)
{
    // todo: replace_all: check that new string doesn't include old string
    size_t find_len = strlen(a_find);
    while (1) {
        size_t pos = str.find (a_find);
        if (pos == string::npos)
            return;
        str.replace (pos, find_len, a_replace);
    }
}

void string_to_vec_delim (const rs_string& wcs, rs_string_v& vec, wchar_t delim)
{
    vec.clear();
    if (wcs.length() == 0)
        return;
    size_t cur_pos = 0;
    while (1) {
        size_t delim_pos = wcs.find (delim, cur_pos);
        if (delim_pos == string::npos) {
            vec.push_back (wcs.substr (cur_pos));
            break;
        }
        vec.push_back (wcs.substr (cur_pos, delim_pos-cur_pos));
        cur_pos = delim_pos+1;
    }
}

void string_to_vec_arg (const rs_string& str, rs_string_v& vec) {
    // parses string representing typical command line
    // arguments are white-space delimited
    // if argument is in double quotes, it can contain white spaces
    vec.clear();
    size_t cur_pos = 0;
    size_t arg_start = 0;
    size_t end_pos = str.length();
    while (1) {
        while (cur_pos < end_pos && 
            (str[cur_pos] == L' '  ||  str[cur_pos] == L'\t'))
            cur_pos++;
        if (cur_pos>=end_pos)
            break; // no more arguments
        // cur_pos is start of argument
        arg_start = cur_pos;
        if (str[cur_pos] == L'"') {
            // quoted argument
            cur_pos++;
            while (cur_pos < end_pos && str[cur_pos] != L'"')
                cur_pos++;
            if (cur_pos < end_pos) {
                vec.push_back (str.substr(arg_start+1, cur_pos-1-arg_start));
                cur_pos++;
            } else {
                vec.push_back (str.substr(arg_start+1));
                break; // no more arguments
            }
        } else {
            // argument till first white space of end of string
            while (cur_pos < end_pos && str[cur_pos] != L' ' 
                    && str[cur_pos] != L'\t')
                cur_pos++;
            if (cur_pos < end_pos) {
                vec.push_back (str.substr(arg_start, cur_pos-arg_start));
            } else {
                vec.push_back (str.substr(arg_start));
                break; // no more arguments
            }
        }
    }
}

void string_to_vec (const rs_string& str, rs_string_v& vec, const rs_string& delim_opt) {
    if (delim_opt == L"")
        string_to_vec_arg (str, vec);
    else
        string_to_vec_delim (str, vec, delim_opt[0]);
}

void vec_to_string (const rs_string_v& vec, rs_string& str, const rs_string& delim_opt)
{
    str = L"";
    if (vec.size() == 0)
        return;
    str = vec[0];
    for (unsigned i=1; i<vec.size(); i++)
        str += delim_opt + vec[i];
}


rs_string nth_substring (const rs_string& str, size_t index, const rs_string& delim_opt)
{   // gets index-th substring from 'str'
    // no warning on no data, just the result is empty
    rs_string result;
    rs_string_v vec;
    string_to_vec (str, vec, delim_opt);
    if (vec.size() > index)
        result = vec[index];
    return result;
}

bool string_to_flags (
    const rs_string& str,
    unsigned& flags,
    const flag_text ft[],
    const rs_string& desc,
    bool allow_several)
{
    vector<rs_string> vec;
    string_to_vec (str, vec, L",");
    if (!allow_several && vec.size() > 1)
        return false;
    bool result (true);
    flags = 0;
    for (size_t ix=0; ix<vec.size(); ix++) {
        rs_string flag_str = vec[ix];
        bool found(false);
        for (const flag_text* ft_ptr=ft;  ft_ptr->ft_flag;  ft_ptr++) {
            if (flag_str == ft_ptr->ft_text) {
                flags |= ft_ptr->ft_flag;
                found = true;
                break;
            }
        }
        if (!found) {
            rs_report_error (L"unknown flag: ",
                L"converting flags '" + str + L"' of type " + desc);
            result = false;
        }
    }
    return result;
}


rs_string flags_to_string (unsigned flags, const flag_text ft[], const rs_string& delim)
{
    rs_string result;
    const flag_text* ft_ptr = ft;
    while (ft_ptr->ft_text) {
        if (flags & ft_ptr->ft_flag) {
            if (result.size() > 0)
                result += delim;
            result += ft_ptr->ft_text;
        }
        ft_ptr++;
    }
    return result;
}


bool parse_item (
    const rs_string& item_str,
    rs_string& item_name,
    unsigned& item_type,
    const flag_text ft[],
    const rs_string& ft_desc)
{
    // .rst files have often strings like L"group:prefixes" or
    item_name = L"";
    item_type = 0;
    vector<rs_string> type_name;
    string_to_vec (item_str, type_name, L":");
    if (type_name.size() != 2) {
        rs_report_error (L"failed to get item type and name: ",
            L"they should be separed with colon (:), but value is " +
            item_str);
        return false;
    }
    item_name = type_name[1];
    return string_to_flags (type_name[0], item_type, ft, ft_desc, false);
}

void strip_regex_start_end (rs_string& pattern, bool& is_start, bool& is_end)
{
    // removes initial "$" and/or final "^" from 'pattern'
    // and sets is_star and is_end according to the presence of "$" and "^"
    is_start = false;
    is_end = false;
    if (pattern.substr(0,1) == L"^") {
        is_start = true;
        pattern = pattern.substr (1);
    }
    size_t len=pattern.length();
    if (len>0  && pattern.substr(len-1,1) == L"$") {
        is_end = true;
        pattern = pattern.substr (0, len-1);
        len=pattern.length();
    }
}


rs_string regex_add_start_end (rs_string pattern, bool is_start, bool is_end)
{
    // if is_start and/or is_end is true,
    // adds initial "$" and/or final "^" to 'pattern'
    // except if supressed by "="
    // character '=' is always removed from pattern
    if (pattern.substr(0,1) == L"=") {
        is_start = false;
        pattern = pattern.substr(1);
    }
    if (is_start)
        pattern = L"^" + pattern;
    size_t len=pattern.length();
    if (len>0  && pattern.substr(len-1,1) == L"=") {
        is_end = false;
        pattern = pattern.substr(0, len-1);
    }
    if (is_end)
        pattern = pattern + L"$";
    return pattern;
}


rs_string rs_stringed (const size_t n, const rs_string& tsep)
{
    // converts number to its string representation
    // inserts a 'tsep' on every 3 digits
    wostringstream woss;
    woss << n;
    rs_string result = woss.str();
    if (tsep.length() > 0) {
        int result_len = static_cast<int> (result.length());
        int i;
        for (i = result_len - 3; i>0; i-=3)
            result.insert (i, tsep);
    }
    return result;
}

enum letter_flags {
    lf_vowel=0x1,
    lf_halfvocal=0x2,
    lf_voiced=0x4,
    lf_voiceless=0x8,
    lf_consonant=0xE,

};
const unsigned lf_letter_any = (lf_vowel | lf_voiced | lf_halfvocal | lf_voiceless);
  // any letter flag, a single letter can have more than one flag
const int ascii_letter_count=26;


const unsigned letter_flag_table[ascii_letter_count]={
    // to do: make it dependent on orthography
    lf_vowel, // a
    lf_voiced, // b
    lf_voiceless, // c
    lf_voiced, // d
    lf_vowel, // e
    lf_voiceless, // f
    lf_voiced, // g
    lf_voiceless, // h
    lf_vowel, // i
    lf_voiced, // j
    lf_voiceless, // k
    lf_halfvocal, // l
    lf_halfvocal, // m
    lf_halfvocal, // n
    lf_vowel, // o
    lf_voiceless, // p
    lf_voiceless, // q
    lf_halfvocal, // r
    lf_voiceless, // s
    lf_voiceless, // t
    lf_vowel, // u
    lf_voiced, // v
    lf_halfvocal, // w
    lf_voiceless, // x
    lf_halfvocal, // y
    lf_voiced, // z
}; // for each letter, suitable affix_flags


inline int char_class_vc (wchar_t wc) {
    // returns either 
    // - lf_vowel if vowel
    // - 0 if character is not letter
    // - some other value for consonant
    int cx = wc - 'a';
    if (cx <0 || cx>=ascii_letter_count)
        return 0;
    return letter_flag_table[cx] & (lf_vowel |  lf_consonant 
        | lf_halfvocal | lf_voiced | lf_voiceless);
}

unsigned count_syllables (const rs_string& word, unsigned &s1, unsigned & s2) {
    // returns the number of syllables in specified word
    // word is expected to be of 26 lowercase letters a-z only
    // if not, result is 0
    // every character is classified as C (consonant) or V (vowel)
    // regular expression like "C+(V+C+){n}" is then checked
    // and result is 'n'
    unsigned wlen = unsigned (word.length());
    s1=s2=wlen;
    if (!is_lower(word))
        return 0;
    unsigned boundaries=0;
    unsigned i;
    bool prev_vowel=false, this_vowel;
    wchar_t wc;
    for (i=0; i<wlen; ) {
        wc = word[i++];
        int cx = wc - 'a';
        this_vowel = (letter_flag_table[cx] & lf_vowel) != 0  || wc==L'y';
        if (prev_vowel != this_vowel) {
            boundaries++;
            prev_vowel = this_vowel;
            if (boundaries==2)
                s1=i;
            else if (boundaries==4)
                s2=i;
        }
    }
    unsigned syl_cnt = (boundaries+1) / 2;
    return unsigned (syl_cnt);
}


